/*
 * 人人信用管理有限公司版权所有.
 * 
 * @Description:
 * 
 * @author: liyuliang
 * 
 * @date: 20 Apr 2017 10:44:16 am
 * 
 * 注意：本内容仅限于人人信用管理有限公司内部传阅，禁止外泄以及用于其他的商业目的.
 */

package com.rrx360.common.dao;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.hibernate.LockOptions;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

import com.rrx360.common.model.Idable;
import com.rrx360.common.model.SoftDeleteAble;
import com.rrx360.common.pagequery.FieldEntry;
import com.rrx360.common.pagequery.PageQueryCondition;
import com.rrx360.common.pagequery.PageQueryResult;
import com.rrx360.common.pagequery.SortEntry;

public class CommonDao {

  protected SessionFactory sessionFactory;

  protected TransactionTemplate transactionTemplate;

  public Session getCurrentSession() {
    return sessionFactory.getCurrentSession();
  }

  public <T> T execute(TransactionCallback<T> cb) {
    return transactionTemplate.execute(cb);
  }


  public void flush() {
    this.getCurrentSession().flush();
  }

  /* 单对象操作 */

  public <T> T create(Class<T> clazz) {
    try {
      return clazz.newInstance();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  public <T> T get(Class<T> clazz, Serializable id) {
    return execute((status) -> {
      return getCurrentSession().get(clazz, id);
    });
  }

  public <T> T load(Class<T> clazz, Serializable id) {
    return execute((status) -> {
      return getCurrentSession().load(clazz, id);
    });
  }

  public <T> T getWithLock(Serializable id, Class<T> clazz) {
    return execute((status) -> {
      return (T) this.getCurrentSession().get(clazz, id, LockOptions.UPGRADE);
    });
  }

  public void evit(Object entity) {
    execute((status) -> {
      this.getCurrentSession().evict(entity);
      return null;
    });
  }

  public void refresh(Object entity) {
    execute((status) -> {
      this.getCurrentSession().refresh(entity);
      return null;
    });
  }

  public Serializable saveEntity(Object entity) {
    return execute((status) -> {
      getCurrentSession().saveOrUpdate(entity);
      if (entity instanceof Idable) {
        return ((Idable) entity).getId();
      }
      return null;
    });
  }

  public void updateEntity(Object obj) {
    execute((status) -> {
      getCurrentSession().saveOrUpdate(obj);
      return null;
    });
  }

  public void saveOrUpdateEntity(Object obj) {
    execute((status) -> {
      getCurrentSession().saveOrUpdate(obj);
      return null;
    });
  }

  protected <T> void deleteByHql(Long id, Class<T> clazz) {
    execute((status) -> {
      Query query = this.getCurrentSession()
          .createQuery("update " + clazz.getName() + " set deleted=1 where id=" + id);
      query.executeUpdate();
      return null;
    });
  }


  public void deleteEntity(Object entity) {
    execute((status) -> {
      if (entity instanceof SoftDeleteAble) {
        ((SoftDeleteAble) entity).setDeleted(true);
        this.getCurrentSession().update(entity);
      }
      return null;
    });
  }


  public void delete(Serializable id, Class clazz) {
    execute((status) -> {
      Object entity = (Object) this.getCurrentSession().get(clazz, id);
      if (entity != null) {
        deleteEntity(entity);
      }
      return null;
    });
  }

  /* 更新操作 */

  protected int update(String hql, Map<String, Object> params) {
    return (int) execute((status) -> {
      Query query = this.getCurrentSession().createQuery(hql);
      this.setParameter(query, params);
      return query.executeUpdate();
    });
  }

  protected int update(String hql, Object[] objects) {
    return (int) execute((status) -> {
      Query query = getCurrentSession().createQuery(hql);
      setParameters(query, objects);
      return query.executeUpdate();
    });
  }

  protected int updateWithParams(String hql, Object... params) {
    return (int) execute((status) -> {
      Query query = getCurrentSession().createQuery(hql);
      Map<String, Object> paramMap = this.getParamMap(hql, params);
      setParameter(query, paramMap);
      return query.executeUpdate();
    });
  }

  /* 列表查询 */
  public <T> List<T> findAll(Class<T> clazz) {
    return execute((status) -> {
      String hql = "from " + clazz.getCanonicalName() + " order by id";
      Query query = this.getCurrentSession().createQuery(hql);
      return query.list();
    });
  }

  protected List find(String hql) {
    return (List) execute((status) -> {
      Query query = this.getCurrentSession().createQuery(hql);
      return query.list();
    });
  }

  protected List find(String hql, Map<String, Object> params) {
    return (List) execute((status) -> {
      Query query = this.getCurrentSession().createQuery(hql);
      this.setParameter(query, params);
      return query.list();
    });
  }
  
  protected List find(String hql, Map<String, Object> params, long offset, int pageSize) {
    return (List) execute((status) -> {
      Query query = this.getCurrentSession().createQuery(hql);
      query.setFirstResult((int) offset);
      query.setMaxResults(pageSize);
      this.setParameter(query, params);
      return query.list();
    });
  }
  
  protected List find(String hql, Object[] objects) {
    return (List) execute((status) -> {
      Query query = getCurrentSession().createQuery(hql);
      setParameters(query, objects);
      return query.list();
    });
  }

  protected List findWithLock(String hql, Map<String, Object> params) {
    return (List) execute((status) -> {
      Query query = this.getCurrentSession().createQuery(hql);
      this.setParameter(query, params);
      query.setLockOptions(LockOptions.UPGRADE);
      return query.list();
    });
  }

  protected <T> T findOne(String hql, Map<String, Object> params) {
    return (T) execute((status) -> {
      Query query = this.getCurrentSession().createQuery(hql);
      query.setMaxResults(1);
      this.setParameter(query, params);
      List<Object> list = query.list();
      return list.size() == 0 ? null : (T) list.get(0);
    });
  }
  
  protected <R> R findOne(String hql, Object[] objects) {
    return (R) execute((status) -> {
      Query query = getCurrentSession().createQuery(hql);
      setParameters(query, objects);

      query.setMaxResults(1);
      List list = query.list();
      if (list.size() == 1) {
        return (R) list.get(0);
      }
      return null;
    });
  }


  protected List findByNamedQuery(String queryName, Map<String, Object> params) {
    return (List) execute((status) -> {
      Query query = this.getCurrentSession().getNamedQuery(queryName);
      setParameter(query, params);
      return query.list();
    });
  }

  protected List findByNamedQuery(String queryName, Map<String, Object> params, int offset,
      int pagesize) {
    return (List) execute((status) -> {
      Query query = this.getCurrentSession().getNamedQuery(queryName);
      setParameter(query, params);
      query.setFirstResult(offset);
      query.setMaxResults(pagesize);
      return query.list();
    });
  }

  protected List findWithLimit(String hql, int limit, Object[] objects) {
    return (List) execute((status) -> {
      Query query = getCurrentSession().createQuery(hql);
      setParameters(query, objects);
      query.setMaxResults(limit);
      return query.list();
    });
  }

  protected List findWithParams(String hql, Object... values) {
    Map<String, Object> paramMap = this.getParamMap(hql, values);
    return this.find(hql, paramMap);
  }


  /* Unique查询 */


  protected <R> R findUniqueWithLock(String hql, Object[] objects) {
    return (R) execute((status) -> {
      Query query = getCurrentSession().createQuery(hql);
      setParameters(query, objects);

      query.setMaxResults(2);
      query.setLockOptions(LockOptions.UPGRADE);
      List list = query.list();
      if (list.size() > 1) {
        throw new RuntimeException("result is not unique");
      }
      if (list.size() == 1) {
        return (R) list.get(0);
      }
      return null;
    });
  }
  
  protected <T> T findUniqueWithLock(String hql, Map<String, Object> params) {
    List list = findWithLock(hql, params);
    if (list.size() == 0) {
      return null;
    }
    if (list.size() > 1) {
      throw new RuntimeException("result not unique");
    }
    return (T) list.get(0);
  }

  protected <R> R findUniqueWithParams(String hql, Object... params) {
    return (R) execute((status) -> {
      Map<String, Object> map = getParamMap(hql, params);
      List list = find(hql, map, 0, 2);
      if (list.size() > 1) {
        throw new RuntimeException("result is not unique");
      }
      if (list.size() == 1) {
        return (R) list.get(0);
      }
      return null;
    });
  }

  protected <R> R findOneWithParams(String hql, Object... params) {
    Map<String, Object> map = getParamMap(hql, params);
    return this.findOne(hql, map);
  }

  protected <T> T findUniqueByCondition(String condition, Class<T> clazz) {
    return (T) findUnique("from " + clazz.getName() + " where " + condition, new Object[] {});
  }


  protected <T> T findUnique(String hql, Map<String, Object> params) {
    List list = find(hql, params);
    if (list.size() == 0) {
      return null;
    }
    if (list.size() > 1) {
      throw new RuntimeException("result not unique");
    }
    return (T) list.get(0);
  }
  
  protected <R> R findUnique(String hql, Object[] objects) {
    return (R) execute((status) -> {
      Query query = getCurrentSession().createQuery(hql);
      setParameters(query, objects);

      query.setMaxResults(2);
      List list = query.list();
      if (list.size() > 1) {
        throw new RuntimeException("result is not unique");
      }
      if (list.size() == 1) {
        return (R) list.get(0);
      }
      return null;
    });
  }

  /* count 查询 */
  public long count(Class clazz) {
    return execute((status) -> {
      String entityName = clazz.getCanonicalName();
      Query query = this.getCurrentSession().createQuery("select count(*) from " + entityName);
      Number number = (Number) query.uniqueResult();
      return number.longValue();
    });
  }

  protected long count(String countHql, Map<String, Object> params) {
    return (long) execute((status) -> {
      Query query = this.getCurrentSession().createQuery(countHql);
      this.setParameter(query, params);
      Number number = (Number) query.uniqueResult();
      return number.longValue();
    });
  }

  protected int countByNamedQuery(String queryName, Map<String, Object> params) {
    return (int) execute((status) -> {
      Session session = this.getCurrentSession();
      Query query = session.getNamedQuery(queryName);
      this.setParameter(query, params);
      Number number = (Number) query.uniqueResult();
      return number.intValue();
    });
  }

  /* 分页查询 */

  public <T> List<T> findByPage(long start, int pageSize, Class<T> clazz) {
    return execute((status) -> {
      Session session = this.getCurrentSession();
      String entityName = clazz.getCanonicalName();
      String hql = "from " + entityName;
      Query query = session.createQuery(hql);
      query.setFirstResult((int) start);
      query.setMaxResults(pageSize);
      return query.list();
    });
  }
  
  public PageQueryResult findByPage(PageQueryCondition condition, Class clazz) {
    return findByPage("from " + clazz.getName(), condition);
  }

  protected PageQueryResult findByPage(String hql, PageQueryCondition condition) {
    QueryEntry entry = new HqlQueryEntry(hql);
    return findByPage(entry, condition);
  }

  protected PageQueryResult findByPage(String hql) {
    QueryEntry entry = new HqlQueryEntry(hql);
    return findByPage(entry);
  }

  protected PageQueryResult findByPage(String hql, String countHql) {
    QueryEntry entry = new HqlQueryEntry(hql, countHql);
    return findByPage(entry);
  }

  protected PageQueryResult findByPage(String hql, String countQl, PageQueryCondition condition) {
    QueryEntry entry = new HqlQueryEntry(hql, countQl);
    return findByPage(entry, condition);
  }
  
  protected <T> List<T> findByPage(String hql, Map<String, Object> params, long start, int pageSize,
      Class<T> clazz) {
    return (List) execute((status) -> {
      Session session = this.getCurrentSession();
      Query query = session.createQuery(hql);
      this.setParameter(query, params);
      query.setFirstResult((int) start);
      query.setMaxResults(pageSize);
      return query.list();
    });
  }
  
  protected PageQueryResult findByPage(QueryEntry entry, PageQueryCondition condition) {
    addPageQueryToQueryEntry(condition, entry);
    return findByPage(entry);
  }

  private PageQueryResult findByPage(QueryEntry entry) {
    entry.setCommonDao(this);
    return new PageQueryResult(entry.execCount(), entry.exec());
  }
  
  protected List findByPage(String hql, Map<String, Object> params, long start, int pageSize) {
    return (List) execute((status) -> {
      Session session = this.getCurrentSession();
      Query query = session.createQuery(hql);
      this.setParameter(query, params);
      query.setFirstResult((int) start);
      query.setMaxResults(pageSize);
      return query.list();
    });
  }


  protected <T> List<T> findByConditionAndPage(String condition, long start, int pageSize,
      Class<T> clazz) {
    return (List) execute((status) -> {
      Session session = this.getCurrentSession();
      String entityName = clazz.getCanonicalName();
      String hql =
          "from " + entityName + (StringUtils.isNotEmpty(condition) ? (" where " + condition) : "");
      Query query = session.createQuery(hql);
      query.setFirstResult((int) start);
      query.setMaxResults(pageSize);
      return query.list();
    });
  }


  protected PageQueryResult findByPageAndSql(String sql, PageQueryCondition condition) {
    // append where if hql has conditions and does not contains where
    QueryEntry entry = new SqlQueryEntry(sql, condition.getStart(), condition.getPageSize());
    return findByPage(entry, condition);
  }

  protected PageQueryResult findByPageAndSql(String sql, String countSql,
      PageQueryCondition condition) {
    // append where if hql has conditions and does not contains where
    QueryEntry entry =
        new SqlQueryEntry(sql, countSql, condition.getStart(), condition.getPageSize());
    return findByPage(entry, condition);
  }


  protected PageQueryResult execQueryEntryForPage(QueryEntry entry) {
    return new PageQueryResult(entry.execCount(), entry.exec());
  }


  /* QueryEntry 相关操作 */

  protected QueryEntry createQueryEntryByPageQueryCondition(String hql,
      PageQueryCondition condition) {
    QueryEntry entry = new HqlQueryEntry(hql, condition.getStart(), condition.getPageSize());
    return entry;
  }



  protected QueryEntry addPageQueryToQueryEntry(PageQueryCondition condition, QueryEntry entry) {
    addPagerToQuery(condition, entry);
    addConListToQuery(condition, entry);
    addSorterToQuery(condition, entry);
    return entry;
  }


  protected QueryEntry addPagerToQuery(PageQueryCondition condition, QueryEntry entry) {
    entry.setStart(condition.getStart());
    entry.setFetchSize(condition.getPageSize());
    return entry;
  }


  protected QueryEntry addConListToQuery(PageQueryCondition condition, QueryEntry entry) {
    /** add where part */
    if (condition.getFieldList() != null) {
      for (FieldEntry con : condition.getFieldList()) {
        entry.addCondition(con.getQueryKey(), con.getQueryKey(), con.getQueryCmp(),
            con.getQueryValue());
      }
    }
    return entry;
  }


  protected QueryEntry addConToQuery(String name, PageQueryCondition condition, QueryEntry entry) {
    FieldEntry con = condition.getField(name);
    if (con != null) {
      entry.addCondition(con.getQueryKey(), con.getQueryKey(), con.getQueryCmp(),
          con.getQueryValue());
    }
    return entry;
  }

  protected QueryEntry addSorterToQuery(PageQueryCondition condition, QueryEntry entry) {
    /** add order part */
    if (CollectionUtils.isNotEmpty(condition.getSortList())) {
      entry.append(" order by");
      for (int i = 0; i < condition.getSortList().size(); i++) {
        SortEntry sortEntry = condition.getSortList().get(i);

        entry.appendToQl(" " + sortEntry.getQueryKey() + " " + sortEntry.getQueryType());
        if (i < condition.getSortList().size() - 1) {
          entry.appendToQl(",");
        }
      }
    }
    return entry;
  }


  /**
   * @param query
   * @param params
   */
  protected void setParameter(Query query, Map<String, Object> params) {
    if (params != null && !params.isEmpty()) {
      for (Map.Entry<String, Object> entry : params.entrySet()) {
        String key = entry.getKey();
        Object value = entry.getValue();
        if (value instanceof Collection) {
          query.setParameterList(key, (Collection) value);
        } else {
          query.setParameter(key, value);
        }
      }
    }
  }

  /**
   * @param ql
   * @param key
   * @param value
   * @param params
   */
  protected void addCondition(StringBuffer ql, String key, Object value,
      Map<String, Object> params) {
    addCondition(ql, key, key, value, params);
  }

  /**
   * 
   * @param ql
   * @param key
   * @param keyParam
   * @param value
   * @param params
   */
  protected void addCondition(StringBuffer ql, String key, String keyParam, Object value,
      Map<String, Object> params) {
    if (value == null || (value instanceof String && "".equals(value))) {
      return;
    }
    ql.append(" and " + key + "=:" + keyParam);
    params.put(keyParam, value);
  }


  protected HqlQueryEntry getHqlQueryEntry(Class<?> clazz) {
    return new HqlQueryEntry(clazz);
  }

  /* SQL操作 */

  protected List<Object[]> selectByColumns(String sql, Map<String, Object> params) {
    return (List) execute((status) -> {
      SQLQuery sqlQuery = this.getCurrentSession().createSQLQuery(sql);
      this.setParameter(sqlQuery, params);
      return sqlQuery.list();
    });
  }

  protected List selectByNativeSql(String sql, Map<String, Object> params) {
    return selectByNativeSql(sql, params, new ArrayList());
  }

  protected List selectByNativeSql(String sql, Map<String, Object> params,
      List<Object> entityList) {
    return (List) execute((status) -> {
      SQLQuery query = this.getCurrentSession().createSQLQuery(sql);
      if (entityList != null) {
        for (Object obj : entityList) {
          if (obj instanceof Class) {
            query.addEntity((Class) obj);
          } else {
            query.addEntity((String) ((Object[]) obj)[0], (Class) ((Object[]) obj)[1]);
          }
        }
      }
      this.setParameter(query, params);
      return query.list();
    });

  }

  protected List selectByNativeSql(String sql, Map<String, Object> params, long start,
      int pageSize) {
    return selectByNativeSql(sql, params, null, start, pageSize);
  }

  protected List selectByNativeSql(String sql, Map<String, Object> params, List<Object> entityList,
      long start, int pageSize) {
    return (List) execute((status) -> {
      SQLQuery query = this.getCurrentSession().createSQLQuery(sql);
      query.setFirstResult((int) start);
      query.setMaxResults(pageSize);
      if (entityList != null) {
        for (Object obj : entityList) {
          if (obj instanceof Class) {
            query.addEntity((Class) obj);
          } else {
            query.addEntity((String) ((Object[]) obj)[0], (Class) ((Object[]) obj)[1]);
          }
        }
      }
      this.setParameter(query, params);
      return query.list();
    });
  }

  protected List selectByNativeSql(String sql, Map<String, Object> params, Class returnClazz) {
    return (List) execute((status) -> {
      SQLQuery query = this.getCurrentSession().createSQLQuery(sql);
      query.addEntity(returnClazz);
      this.setParameter(query, params);
      return query.list();
    });
  }

  protected long updateByNativeSql(String sql, Map<String, Object> params) {
    return (long) execute((status) -> {
      SQLQuery sqlQuery = this.getCurrentSession().createSQLQuery(sql);
      this.setParameter(sqlQuery, params);
      int count = sqlQuery.executeUpdate();
      return count;
    });
  }

  protected long countByNativeSql(String countSql, Map<String, Object> params) {
    return (long) execute((status) -> {
      SQLQuery query = this.getCurrentSession().createSQLQuery(countSql);
      this.setParameter(query, params);
      Number number = (Number) query.uniqueResult();
      if (number == null) {
        return 0;
      }
      return number.longValue();
    });
  }

  /* 辅助方法 */

  private void setParameters(Query query, Object[] objects) {
    if (objects != null && objects.length != 0) {
      for (int i = 0; i < objects.length; i += 2) {
        if (objects[i + 1] instanceof Collection) {
          query.setParameterList((String) objects[i], (Collection) objects[i + 1]);
        } else {
          query.setParameter((String) objects[i], objects[i + 1]);
        }
      }
    }
  }

  /**
   * Get hibernate params from hql and values. Parse the hql and get the paramKey by seperator. The
   * paramKey is between ':' and ' ' or in hql's last.
   * 
   * @param hql
   * @param values
   * @return
   */
  protected Map<String, Object> getParamMap(String hql, Object... values) {
    Map<String, Object> map = new HashMap<String, Object>();
    if (hql == null || values == null || values.length == 0) {
      return map;
    }
    int index = 0;
    int start = -1;
    for (int i = 0; i < hql.length(); i++) {
      char c = hql.charAt(i);
      if (':' == c) {
        start = i + 1;
        continue;
      }
      if (start != -1 && (' ' == c || ')' == c)) {
        map.put(hql.substring(start, i), values[index]);
        start = -1;
        index++;
      }
    }
    if (start != -1) {
      map.put(hql.substring(start), values[index]);
    }
    return map;
  }


  protected String getWherePart(String condition) {
    return StringUtils.isNotEmpty(condition) ? (" where " + condition) : "";
  }
}
